package com.sensetime.bi.senselink.server.rtsptest;


import java.io.*;
import java.net.*;
import java.util.Timer;

public class RtspCtl {
    private int serverPort = 554;
    private String hostName = null;
    private String videoFile = null;
    // Flag to establish that we have set up our parameters
    private boolean isSetup = false;
    public Socket RTSPSocket = null;
    public BufferedReader RTSPBufferedReader;
    public BufferedWriter RTSPBufferedWriter;
    // Current state of the client
    private RTSPState state = null;
    // Sequence number of RTSP messages within the session
    // Initially set to zero
    private int RTSPSeqNb = 0;
    // as the actual RTSP server I'm testing on issues sessions
    // in alphanumeric characters.  Leave this as a string to be safe
    public String RTSPSessionID = null; // ID of RTSP sessions - given by RTSP server
    // Carriage return and new line to send to server
    private final String CRLF = "\r\n";

    // For the audio and video track IDs issued from the server
    public int audioTrackID = -1;
    public int videoTrackID = -1;
    // Store the audio and video payload types
    // We will need this to figure out what data is being sent from the server
    public int audioPT = -1;
    public int videoPT = -1;
    // Flags that tell us whether SETUP using video and audio are finished
    private boolean videoSetupDone = false;
    private boolean audioSetupDone = false;

    // Buffer to receive the data
    byte[] buf = null;
    // The total number of tracks
    public int numberOfTracks = 0;

    // Socket used to send and receive UDP packets
    public DatagramSocket RTPsocket = null;

    // Timer used to receive data from the UDP socket
    Timer timer = null;
    Timer timerOptions = null;

    // Variables that define the periodic options request sent to the
    // RTSP server
    // In order to prevent a time out, it's good to periodically send
    // something to the server to let you know that you're still here
    // You can do this very innocuously with an OPTIONS request
    // As such, every 45 seconds we send an OPTIONS request to let
    // the server know we're still here
    // We will also start the timer at about 15 seconds after we schedule
    // the task to ensure no conflicts
    private final int TIMEROPTIONSDELAY = 500000;
    private final int TIMEROPTIONSFREQUENCY = 100000;

    // Boolean flag that tells us whether or not the ID of the track is referenced by the key "trackID=" or "stream="
    // GStreamer has it as the latter, while conventional RTSP servers have it as the former
    boolean trackIDOrStream = true;

    // Flag to determine if the trackID string is just trackID or track
    public boolean galbarmFlag = false;

    // The type of streaming protocol extracted from the server
    public String streamingProtocolVideo = null;
    public String streamingProtocolAudio = null;

    // Port where client will receive the RTP packets
    private int RTP_RCV_PORT = 9000;

    // UDP packet received from server
    public DatagramPacket rcvdp = null;


    public RtspCtl(String serverHost, int serverPort, String fileName) {
        if (fileName == null) {
            throw new IllegalArgumentException("RTSPTest: Filename must not be null");
        }

        if (serverHost == null) {
            throw new IllegalArgumentException("RTSPTest: Server host must not be null");
        }

        if (serverPort < -1 || serverPort > 65535) {
            throw new IllegalArgumentException("RTSPTest: Port must be between 0 and 65535");
        }

        // Figure out the server port
        if (serverPort == -1)
        {
            this.serverPort = 554;
        } else {
            this.serverPort = serverPort;
        }

        // Store host name
        hostName = new String(serverHost);
        // Store file name
        videoFile = new String(fileName);

        // Set to false then set up
        isSetup = false;
        setUpConnectionAndParameters();
    }

    private void setUpConnectionAndParameters() {
        try {
            InetAddress serverIpAddr = InetAddress.getByName(this.hostName);
            this.RTSPSocket = new Socket(serverIpAddr, this.serverPort);
            this.RTSPBufferedReader = new BufferedReader(new InputStreamReader(RTSPSocket.getInputStream()));
            this.RTSPBufferedWriter = new BufferedWriter(new OutputStreamWriter(RTSPSocket.getOutputStream()));
            this.state = RTSPState.INIT;

            // Initialize buffer to capture enough bytes from the server
            this.buf = new byte[300000];
            // Initialize parameters
            this.audioTrackID = -1;
            this.videoTrackID = -1;
            this.audioPT = -1;
            this.videoPT = -1;
            this.RTSPSessionID = null;
            this.numberOfTracks = 0;
            this.isSetup = true;
            this.RTPsocket = null;
            this.videoSetupDone = false;
            this.videoSetupDone = false;

            this.timerOptions = new Timer();
            this.timerOptions.scheduleAtFixedRate(new RTSPOptionsTimerTask(this), TIMEROPTIONSDELAY, TIMEROPTIONSFREQUENCY);

        } catch (UnknownHostException e) {
            System.out.println("Could not find host");
            e.printStackTrace();
        } catch (IOException e) {
            System.out.println("Could not establish socket");
            e.printStackTrace();
        }
    }

    // Send a request to see what the options are
    public void RTSPOptions() {
        // Remove because we would like to send an OPTIONS request no matter what
        // state we are in
        //if (state != RTSPState.INIT) {
        //  System.out.println("Must be in INIT stage before requesting Options");
        //  return;
        //}

        if (!isSetup) {
            setUpConnectionAndParameters();
        }

        // Increase RTSP Sequence Number
        RTSPSeqNb++;

        // Send OPTIONS message to the server
        sendRTSPRequest(RTSPRequestType.OPTIONS);

        // Wait for server response
        if (RtspResponseParse.parseServerResponse(this) != 200) {
            System.out.println("Invalid Server Response");
        } else {
            System.out.println("Options Request succeeded");
            // We don't need to change the state
        }
    }

    // Integer code - 0 for no more setup calls required
    // >= 1 - An additional setup is required
    // -1 - Invalid server response
    public int RTSPSetup() {
        if (state != RTSPState.INIT) {
            System.out.println("Client is already set up or is playing content");
            return 0; // We have already set up or are playing
        }

        if (numberOfTracks == 0) {
            System.out.println("No tracks to set up!");
            return 0;
        }

        // Cancel the options timer request while we do this
        if (timerOptions != null) {
            timerOptions.cancel();
        }

        // Increment RTSP sequence number
        RTSPSeqNb++;

        // Send SETUP message to server
        sendRTSPRequest(RTSPRequestType.SETUP);

        // Wait for response code from server
        if (RtspResponseParse.parseServerResponse(this) != 200) {
            System.out.println("Invalid Server Response");
            return -1;
        }
        // Check to see what track IDs we have
        // If we have both, then we need to wait for another
        // setup call
        // We aren't ready until we set up all of our
        // tracks
        if (numberOfTracks == 0) {
            // Change current RTSP state to READY
            System.out.println("Client is ready");
            state = RTSPState.READY;

            // Also establish our socket connection to the server
            if (RTPsocket == null) {
                try {
                    RTPsocket = new DatagramSocket(RTP_RCV_PORT);

                    // Set timeout value to 100 msec
                    // Disable for now
                    //RTPsocket.setSoTimeout(100);
                }

                // Catch exception here
                catch (SocketException se) {
                    System.out.println("Socket Exception: " + se);
                    System.exit(0);
                }
            }
        }

        // Restart Options timer
        timerOptions = new Timer();
        timerOptions.scheduleAtFixedRate(new RTSPOptionsTimerTask(this), TIMEROPTIONSDELAY,
                TIMEROPTIONSFREQUENCY);
        return numberOfTracks;
    }

    // Send a request for DESCRIBING what is available
    public void RTSPDescribe() {
        if (state != RTSPState.INIT) {
            System.out.println("Must be in INIT stage before requesting DESCRIBE");
            return;
        }

        if (!isSetup) {
            setUpConnectionAndParameters();
        }

        // Cancel Options timer while we pull this information
        if (timerOptions != null) {
            timerOptions.cancel();
        }

        // Increase RTSP Sequence Number
        RTSPSeqNb++;

        // Send OPTIONS message to the server
        sendRTSPRequest(RTSPRequestType.DESCRIBE);

        // Wait for server response
        if (RtspResponseParse.parseServerResponse(this) != 200) {
            System.out.println("Invalid Server Response");
        } else {
            System.out.println("Describe Request succeeded");
            // We don't need to change the state
        }

        // Restart Options timer
        timerOptions = new Timer();
        timerOptions.scheduleAtFixedRate(new RTSPOptionsTimerTask(this), TIMEROPTIONSDELAY,
                TIMEROPTIONSFREQUENCY);
    }

    public void RTSPPlay() {
        if (state != RTSPState.READY) {
            System.out.println("Client has not sent Setup Request yet");
            return;
        }

        // Cancel Options timer while we pull the response from the server
        if (timerOptions != null) {
            timerOptions.cancel();
        }

        // Increase the RTSP sequence number - This is the
        // next command to issue
        RTSPSeqNb++;

        // Send PLAY message to server
        sendRTSPRequest(RTSPRequestType.PLAY);

        // Wait for response
        if (RtspResponseParse.parseServerResponse(this) != 200) {
            System.out.println("Invalid Server Response");
            return;
        }

        System.out.println("Starting playback - Starting Timer event");
        // Set to play state
        state = RTSPState.PLAYING;

        if (RTPsocket != null) {
            try {
                if (RTPsocket.isClosed()) {
                    RTPsocket = new DatagramSocket(RTP_RCV_PORT);
                }
            } catch (SocketException e) {
                System.out.println("Could not reconnect to socket");
                e.printStackTrace();
            }
        }

        // Initialize timer to receive events from server
        timer = new Timer();
        // Start it now, with 20 millisecond intervals
//        timer.scheduleAtFixedRate(new RtpPkgProcess(this), 0, 20);
        timer.scheduleAtFixedRate(new RtpPkgProcessTcp(this), 0, 20);

        // Restart Options timer event
        // Even if we are playing content, we still need to keep our
        // connection alive
        timerOptions = new Timer();
        timerOptions.scheduleAtFixedRate(new RTSPOptionsTimerTask(this), TIMEROPTIONSDELAY,
                TIMEROPTIONSFREQUENCY);
    }
    public void RTSPTeardown() {
        // You can only call TEARDOWN after the connections have been set up,
        // or if you are playing content
        if (state == RTSPState.INIT) {
            System.out.println("Client is in initialize stage - No need to teardown");
            return;
        }

        // Increase RTSP Sequence number
        RTSPSeqNb++;

        // Send TEARDOWN message to the server
        sendRTSPRequest(RTSPRequestType.TEARDOWN);

        // Wait for server response
        if (RtspResponseParse.parseServerResponse(this) != 200) {
            System.out.println("Invalid Server Response");
        } else {
            System.out.println("Teardown - Changing Client state back to INIT");

            // Reset all parameters
            resetParameters();
        }
    }

    public void resetParameters() {
        state = RTSPState.INIT;

        // Reset sequence number
        RTSPSeqNb = 0;

        // Reset all other parameters
        audioTrackID = -1;
        videoTrackID = -1;
        audioPT = -1;
        videoPT = -1;
        RTSPSessionID = null;
        numberOfTracks = 0;
        isSetup = false;
        buf = null;
        videoSetupDone = false;
        audioSetupDone = false;

        // Cancel all timing events if Teardown option is executed
        if (timer != null) {
            timer.cancel();
            timer = null;
        }

        if (timerOptions != null) {
            timerOptions.cancel();
            timerOptions = null;
        }

        // Close connection as well
        if (RTPsocket != null && !RTPsocket.isClosed()) {
            RTPsocket.close();
            RTPsocket = null;
        }
    }


    public void sendRTSPRequest(RTSPRequestType request) {
        // Set up base String
        String requestType = request.getType();
        StringBuilder stringToSend = new StringBuilder();
        System.out.println("================================== Will send request type:" + requestType);

        // This handles actual strings we are going to send to the server
        switch(request) {
            case SETUP:
                if (videoSetupDone && audioSetupDone) {
                    System.out.println("Setup already established");
                    break;
                }

                try {
                    // Set up video track if we haven't done it yet
                    if (videoTrackID != -1 && !videoSetupDone) {
                        System.out.println("*** Setting up Video Track: ");
                        if (this.trackIDOrStream) {
                            if (videoFile != null) {
                                //////// Fix thanks to galbarm
                                if (this.galbarmFlag) {
                                    stringToSend.append(requestType + " rtsp://" + hostName + ":" + serverPort +
                                            "/" + videoFile + "/track=" + videoTrackID + " RTSP/1.0" + CRLF);
                                } else {
                                    stringToSend.append(requestType + " rtsp://" + hostName + ":" + serverPort +
                                            "/" + videoFile + "/trackID=" + videoTrackID + " RTSP/1.0" + CRLF);
                                }
                            } else {
                                if (this.galbarmFlag) {
                                    stringToSend.append(requestType + " rtsp://" + hostName + ":" + serverPort +
                                            "/track=" + videoTrackID + " RTSP/1.0" + CRLF);
                                } else {
                                    stringToSend.append(requestType + " rtsp://" + hostName + ":" + serverPort +
                                            "/trackID=" + videoTrackID + " RTSP/1.0" + CRLF);
                                }
                            }
                        } else {
                            if (videoFile != null) {
                                stringToSend.append(requestType + " rtsp://" + hostName + ":" + serverPort +
                                        "/" + videoFile + "/streamid=" + videoTrackID + " RTSP/1.0" + CRLF);
                            } else {
                                stringToSend.append(requestType + " rtsp://" + hostName + ":" + serverPort +
                                        "/streamid=" + videoTrackID + " RTSP/1.0" + CRLF);
                            }
                        }

                        stringToSend.append("CSeq: " + RTSPSeqNb + CRLF);

                        if (RTSPSessionID != null) {
                            stringToSend.append("Session: " + RTSPSessionID + CRLF);
                        }

//                        stringToSend.append("Transport: " + streamingProtocolVideo + ";unicast;client_port=" +
//                                RTP_RCV_PORT + "-" + (RTP_RCV_PORT+1) + CRLF + CRLF);
                        stringToSend.append("Transport: " + streamingProtocolVideo + "/TCP;unicast;interleaved=0-1" + CRLF + CRLF);
                        RTSPBufferedWriter.write(stringToSend.toString());
                        System.out.println(stringToSend.toString());
                        RTSPBufferedWriter.flush();
                        numberOfTracks--;
                        videoSetupDone = true;
                    }

                    // After, set up the audio track
                    else if (audioTrackID != -1 && !audioSetupDone) {
                        System.out.println("*** Setting up Audio Track: ");
                        if (this.trackIDOrStream) {
                            if (videoFile != null) {
                                if (this.galbarmFlag) {
                                    stringToSend.append(requestType + " rtsp://" + hostName + ":" + serverPort +
                                            "/" + videoFile + "/track=" + audioTrackID + " RTSP/1.0" + CRLF);
                                } else {
                                    stringToSend.append(requestType + " rtsp://" + hostName + ":" + serverPort +
                                            "/" + videoFile + "/trackID=" + audioTrackID + " RTSP/1.0" + CRLF);
                                }
                            }
                            else {
                                if (this.galbarmFlag) {
                                    stringToSend.append(requestType + " rtsp://" + hostName + ":" + serverPort +
                                            "/track=" + audioTrackID + " RTSP/1.0" + CRLF);
                                } else {
                                    stringToSend.append(requestType + " rtsp://" + hostName + ":" + serverPort +
                                            "/trackID=" + audioTrackID + " RTSP/1.0" + CRLF);
                                }
                            }
                        }
                        else {
                            if (videoFile != null) {
                                stringToSend.append(requestType + " rtsp://" + hostName + ":" + serverPort +
                                        "/" + videoFile + "/stream=" + audioTrackID + " RTSP/1.0" + CRLF);
                            } else {
                                stringToSend.append(requestType + " rtsp://" + hostName + ":" + serverPort +
                                        "/stream=" + audioTrackID + " RTSP/1.0" + CRLF);
                            }

                        }

                        stringToSend.append("CSeq: " + RTSPSeqNb + CRLF);

                        if (RTSPSessionID != null) {
                            stringToSend.append("Session: " + RTSPSessionID + CRLF);
                        }

                        stringToSend.append("Transport: " + streamingProtocolAudio + ";unicast;client_port=" +
                                RTP_RCV_PORT + "-" + (RTP_RCV_PORT+1) + CRLF + CRLF);
                        RTSPBufferedWriter.write(stringToSend.toString());
                        System.out.println(stringToSend.toString());
                        RTSPBufferedWriter.flush();
                        numberOfTracks--;
                        audioSetupDone = true;
                    }
                } catch(IOException e) {
                    System.out.println("Could not write to write buffer");
                    e.printStackTrace();
                }
                break;
            case DESCRIBE: // Make sure we call DESCRIBE first
            case PLAY: // Case when we wish to issue a PLAY request
            case PAUSE: // Same for PAUSE
            case TEARDOWN: // Also same for TEARDOWN
            case OPTIONS: // Also same for OPTIONS
                try {
                    if (videoFile != null) {
                        stringToSend.append(requestType + " rtsp://" + hostName + ":" + serverPort +
                                "/" + videoFile + " RTSP/1.0" + CRLF);
                    } else {
                        stringToSend.append(requestType + " rtsp://" + hostName + ":" + serverPort +
                                "/ RTSP/1.0" + CRLF);
                    }

                    // Send sequence number
                    // If there is no session ID, this is the last thing we send
                    if (RTSPSessionID == null) {
                        stringToSend.append("CSeq: " + RTSPSeqNb + CRLF + CRLF);
                    }
                        // Send session number if applicable
                    else {
                        stringToSend.append("CSeq: " + RTSPSeqNb + CRLF);
                        stringToSend.append("Session: " + RTSPSessionID + CRLF);
                        stringToSend.append("Range: npt=0.000-" + CRLF + CRLF);
                    }
                    RTSPBufferedWriter.write(stringToSend.toString());
                    System.out.println(stringToSend.toString());
                    RTSPBufferedWriter.flush();
                }
                catch (IOException e) {
                    System.out.println("Could not write to write buffer");
                    e.printStackTrace();
                }
                break;
            default:
                throw new RuntimeException("Invalid Client State");
        }
    }

    public static void main(String[] args) {
        System.out.println("test");
//        RtspCtl ctl = new RtspCtl("10.4.10.135", 9554, "test.mp4");
        RtspCtl ctl = new RtspCtl("127.0.0.1", 554, "cnRzcDovL2d1b3dlbjE6R29vZHNlbnNlQDEwLjQuMTg5Ljcw");
        ctl.RTSPOptions();
        ctl.RTSPDescribe();
        int resp = ctl.RTSPSetup();
        System.out.println("setup resp=" + resp);
        ctl.RTSPPlay();
//        ctl.RTSPTeardown();
//
//        ctl.RTSPOptions();
//        ctl.RTSPDescribe();
//        resp = ctl.RTSPSetup();
//        System.out.println("setup resp=" + resp);
//        ctl.RTSPPlay();
    }
}
